package com.fzf.org.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fzf.org.dao.entities.AbnormalUserDO;
import com.fzf.org.dao.entities.BanedUser;
import com.fzf.org.dao.entities.UserDO;
import com.fzf.org.dao.mapper.AbnormalUserMapper;
import com.fzf.org.dao.mapper.BanedUserMapper;
import com.fzf.org.dao.mapper.UserMapper;
import com.fzf.org.dto.net.RedisInfoDTO;
import com.fzf.org.dto.req.user.UserLoginReqDTO;
import com.fzf.org.dto.req.user.UserRegisterReqDTO;
import com.fzf.org.dto.req.user.UserUpdateReqDTO;
import com.fzf.org.dto.resp.user.UserSelfRespDTO;
import com.fzf.org.exception.BizException;
import com.fzf.org.exception.BizExceptionTypeEnum;
import com.fzf.org.service.UserService;
import com.fzf.org.toolkit.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.util.Date;
import java.util.List;
import java.util.Optional;

import static com.fzf.org.toolkit.ConstantKey.FIXED_RATE;

@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {

    private static final String USER_PREFIX = "user";
    private final UserMapper userMapper;
    private final BanedUserMapper banedUserMapper;
    private final AbnormalUserMapper abnormalUserMapper;

    private final RedisBloomFilterService redisBloomFilterService;
    private final FileStorageService fileStorageService;
    private final RedisOperateService redisOperateService;
    private final SliderCheckService sliderCheckService;


    /**
     * @param account 用户账号
     * @return 当前账号是否已经存在于mysql之中
     */
    @Override
    public Boolean checkAccountIfExist(String account) {
        Long l = userMapper.selectCount(new LambdaQueryWrapper<UserDO>().eq(UserDO::getAccount, account));
        return l != 0;
    }

    /**
     * 在1分钟之内登录三次均失败之后会标记当前账号是一个异常账号，下次登录需要进行滑块验证操作，登录成功之后清除异常
     *
     * @param account 用户账号
     * @return 当前账号是否是一个正常的账号
     */
    @Override
    public Boolean checkAccountIfNormal(String account) {
        long count = redisOperateService.getFailCount(USER_PREFIX, account);
        if (count >= 3) {
            return false;
        }
        UserDO user = userMapper.selectOne(new LambdaQueryWrapper<UserDO>().eq(UserDO::getAccount, account));
        return user == null || user.getNormal();

    }

    /**
     * 存储用户上传的头像，要求只能上传 png、jpg、jpeg 格式的图片
     *
     * @param picture 用户头像
     */
    @Override
    @Transactional(rollbackFor = BizException.class)
    public void uploadPicture(MultipartFile picture) {
        String originalFilename = picture.getOriginalFilename();
        String type = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
        if ("png".equals(type) || "jpg".equals(type) || "jpeg".equals(type)) {
            String url = Optional.ofNullable(fileStorageService.of(picture).upload()).map(FileInfo::getUrl).orElseThrow(
                    () -> new BizException(BizExceptionTypeEnum.COS_UPLOAD_ERROR)
            );
            UserDO user = UserDO.builder()
                    .id(BasicContext.getBasicId())
                    .pictureUrl(url)
                    .build();
            int update = userMapper.updateById(user);
            if (update != 1) {
                throw new BizException(BizExceptionTypeEnum.USER_UPDATE_ERROR);
            }
        }else{
            throw new BizException(BizExceptionTypeEnum.NOT_ALLOWED_FILE_TYPE);
        }
    }

    /**
     * @return 用户的基本信息
     */
    @Override
    public UserSelfRespDTO profile() {
        UserDO user = userMapper.selectById(BasicContext.getBasicId());
        return BeanUtil.copyProperties(user, UserSelfRespDTO.class);
    }

    /**
     * 用户注册
     *
     * @param userRegisterReqDTO 用户注册信息
     */
    @Override
    @Transactional(rollbackFor = BizException.class)
    public boolean register(UserRegisterReqDTO userRegisterReqDTO) {
        Long l = userMapper.selectCount(new LambdaQueryWrapper<UserDO>().eq(UserDO::getAccount, userRegisterReqDTO.getAccount()));
        if (l == 0) {
            UserDO userDO = UserDO.builder()
                    .account(userRegisterReqDTO.getAccount())
                    .password(Argon2PasswordEncoder.encode(userRegisterReqDTO.getPassword())) // argon2 对密码加密
                    .name(userRegisterReqDTO.getName())
                    .phone(userRegisterReqDTO.getPhone())
                    .roleId(1L)
                    .normal(true)
                    .build();
            int insert = userMapper.insert(userDO);
            if (insert != 1) {
                throw new BizException(BizExceptionTypeEnum.USER_INSERT_ERROR);
            }

            redisBloomFilterService.addAccount(userDO.getAccount());
            return true;
        }
        throw new BizException(BizExceptionTypeEnum.USER_EXIST_ERROR);
    }

    @Override
    public void updateUserInfo(UserUpdateReqDTO userUpdateReqDTO) {
        UserDO user = BeanUtil.copyProperties(userUpdateReqDTO, UserDO.class);
        user.setId(BasicContext.getBasicId());
        if(null != userUpdateReqDTO.getPassword()){
            user.setOnline(false);
            user.setPassword(Argon2PasswordEncoder.encode(userUpdateReqDTO.getPassword()));
            int update = userMapper.updateById(user);
            if (update != 1) {
                throw new BizException(BizExceptionTypeEnum.USER_UPDATE_ERROR);
            }
            // 删除redis中包含用户信息的实体数据
            redisOperateService.deleteInfo(USER_PREFIX, BasicContext.getBasicAccount());
            // 删除redis的登录管理zset中插入已经登录的用户id
            redisOperateService.removeFromLoginExpireZSet(USER_PREFIX, BasicContext.getBasicAccount());
            // 将token放入黑名单中
            redisOperateService.addTokenToBlackTokenZset(BasicContext.getAuthorizationToken());
        }else{
            int update = userMapper.updateById(user);
            if (update != 1) {
                throw new BizException(BizExceptionTypeEnum.USER_UPDATE_ERROR);
            }
        }
    }

    // 检查本次登录请求是否是一个正确的滑块验证请求
    private boolean isSliderRequest(UserLoginReqDTO userLoginReqDTO) {
        return StringUtils.hasText(userLoginReqDTO.getTicket()) &&
                StringUtils.hasText(userLoginReqDTO.getRandStr()) &&
                StringUtils.hasText(userLoginReqDTO.getUserIp());
    }

    private boolean checkSlider(UserLoginReqDTO userLoginReqDTO) {
        return sliderCheckService.checkSlider(userLoginReqDTO.getTicket(),
                userLoginReqDTO.getRandStr(),
                userLoginReqDTO.getUserIp());
    }

    private String processAndGetToken(UserDO user) {
        if (null != redisOperateService.getInfo(USER_PREFIX, user.getAccount())) { // 禁止重复登录
            throw new BizException(BizExceptionTypeEnum.USER_REPEAT_LOGIN);
        }

        String JWTToken = JWTAuthenticator.generateToken(user);

        user.setOnline(true);
        user.setLastLoginTime(new Date(System.currentTimeMillis()));
        int update = userMapper.updateById(user);// 修改用户登录状态
        if (update != 1) {
            throw new BizException(BizExceptionTypeEnum.USER_UPDATE_ERROR);
        }
        // 生成redis存储的对象信息实体
        RedisInfoDTO redisInfoDTO = BeanUtil.copyProperties(user, RedisInfoDTO.class);
        redisInfoDTO.setToken(JWTToken);

        // 向redis新增包含用户信息的实体数据
        redisOperateService.setInfoWithExpireTIme(USER_PREFIX, redisInfoDTO.getAccount(), redisInfoDTO);
        // 向redis的登录管理zset中插入已经登录的用户id
        redisOperateService.addToLoginExpireZset(USER_PREFIX, redisInfoDTO.getAccount());

        return JWTToken;
    }

    /**
     * @param userLoginReqDTO 用户登录信息
     * @return 成功登录则返回生成的JWT token
     */
    @Override
    public String login(UserLoginReqDTO userLoginReqDTO) {
        if(!redisBloomFilterService.containsAccount(userLoginReqDTO.getAccount())){
            throw new BizException(BizExceptionTypeEnum.USER_NOT_EXIST_ERROR);
        }

        Long l = banedUserMapper.selectCount(// 先判断是否已经被封号
                new LambdaQueryWrapper<BanedUser>().eq(BanedUser::getAccount, userLoginReqDTO.getAccount())
        );
        if (l == 0) {// 正常用户，进入正常登录逻辑判断
            if (isSliderRequest(userLoginReqDTO)) { // 是否本次请求带有滑块验证
                if (checkSlider(userLoginReqDTO)) { // 通过验证进入下一步数据库比对，否则视为错误请求
                    UserDO user = userMapper.selectOne(
                            new LambdaQueryWrapper<UserDO>()
                                    .eq(UserDO::getAccount, userLoginReqDTO.getAccount())
                    );
                    if (null == user ||
                            !Argon2PasswordEncoder.matches(userLoginReqDTO.getPassword(), user.getPassword())) {
                        throw new BizException(BizExceptionTypeEnum.USER_ACCOUNT_OR_PWD_ERROR);
                    } else {
                        return processAndGetToken(user);
                    }
                }
                throw new BizException(BizExceptionTypeEnum.USER_REQUEST_ERROR);
            } else {
                UserDO user = userMapper.selectOne(
                        new LambdaQueryWrapper<UserDO>()
                                .eq(UserDO::getAccount, userLoginReqDTO.getAccount())
                                .eq(UserDO::getNormal, true)
                );
                if (user == null ||
                        !Argon2PasswordEncoder.matches(userLoginReqDTO.getPassword(), user.getPassword())) { // 失败次数记录

                    long count = redisOperateService.getAndSetFailCount(USER_PREFIX, userLoginReqDTO.getAccount());
                    System.out.println(count);
                    if (count == 3) {

                        int update = userMapper.update(UserDO.builder().normal(false).build(), new LambdaQueryWrapper<UserDO>()
                                .eq(UserDO::getAccount, userLoginReqDTO.getAccount()));
                        System.out.println(update);
                        if (update != 1) {
                            throw new BizException(BizExceptionTypeEnum.USER_UPDATE_ERROR);
                        }

                        AbnormalUserDO abnormalUserDO = AbnormalUserDO.builder()
                                .account(userLoginReqDTO.getAccount())
                                .latestFailTime(new Date(System.currentTimeMillis()))
                                .build();
                        int insert = abnormalUserMapper.insert(abnormalUserDO);
                        if (insert != 1) {
                            throw new BizException(BizExceptionTypeEnum.USER_ABNORMAL_INSERT_ERROR);
                        }

                    }
                    throw new BizException(BizExceptionTypeEnum.USER_ACCOUNT_OR_PWD_ERROR);
                } else { // 返回正确的 jwt token
                    return processAndGetToken(user);
                }
            }
        }
        throw new BizException(BizExceptionTypeEnum.USER_FORBIDDEN_ERROR);
    }

    @Override
    public void logout() {
        UserDO user = UserDO.builder()
                .id(BasicContext.getBasicId())
                .online(false)
                .build();
        int update = userMapper.updateById(user);
        if (update != 1) {
            throw new BizException(BizExceptionTypeEnum.USER_UPDATE_ERROR);
        }

        // 删除redis中包含用户信息的实体数据
        redisOperateService.deleteInfo(USER_PREFIX, BasicContext.getBasicAccount());
        // 删除redis的登录管理zset中插入已经登录的用户id
        redisOperateService.removeFromLoginExpireZSet(USER_PREFIX, BasicContext.getBasicAccount());
        // 将token放入黑名单中
        redisOperateService.addTokenToBlackTokenZset(BasicContext.getAuthorizationToken());
    }

    @Scheduled(fixedDelay = FIXED_RATE) // 执行完一次后计时
    public void scheduledUpdateUserStatus() {
        System.out.println("user-批量进行数据库更新状态操作");
        List<String> userAccounts = redisOperateService.
                getAndRemoveFromLoginExpireZSetByRange(USER_PREFIX);
        if (null == userAccounts || userAccounts.isEmpty()) {
            return;
        }
        userMapper.batchUpdateOnlineToFalse(userAccounts);
    }

    @Scheduled(fixedDelay = FIXED_RATE) // 执行完一次后计时
    public void scheduledRemoveToken() {
        System.out.println("删除黑名单过期token操作");
        redisOperateService.removeTokenFromBlackTokenZset();
    }
}